// * 在 JS 中，函数也是一个值（就是说它会返回一个值或者被赋予其他值）。
function fn() {
	return 1
}

console.log(fn()) // 1

fn = 2

console.log(fn) // 2

// *****************************************************************************************************

// * JS 中函数的类型是对象（一个可被调用的“行为对象”）。我们可以把它当作对象来处理：增/删属性，按引用传递。

// * 属性 name（函数对象包含一些便于使用的属性）。

// 通过 name 来访问函数的名字

// 例1：
function sayHi() {
	console.log('Hi')
}
console.log(sayHi.name) // sayHi

// 例2：
let sayHi2 = function () {
	console.log('Hi')
}
console.log(sayHi2.name) // sayHi2

// 例3：
function f(sayHi3 = function () {}) {
	console.log(sayHi3.name) // sayHi3
}
f()
console.log(f.name) // f

// 例4：
let user = {
	sayHis() {},
	sayBye: function () {},
}
console.log(user.sayHis.name, user.sayBye.name) // sayHis sayBye

// 例5：没有指定名字的匿名函数，name 属性为 空字符串。
let arr = [function () {}]
console.log(arr[0].name) // ""

// *****************************************************************************************************

// * 属性 length（返回函数入参的个数）
function f1(a) {}
function f2(a, b) {}
function f3(a, b, ...rest) {} // rest 参数不计入其中
console.log(f1.length, f2.length, f3.length) // 1 2 2

// * 实际场景使用例子：
function ask(question, ...handlers) {
	let isYes = confirm(question)

	for (const handler of handlers) {
		if (handler.length == 0) {
			// 无参函数，它仅在用户给出肯定回答时被调用。
			if (isYes) handler()
		} else {
			// 有参函数，它在两种情况都会被调用，并且返回一个答案。
			handler(isYes)
		}
	}
}

// 这个例子，就是所谓的 多态性 —— 根据参数的类型，或者根据在我们的具体情景下的 length 来做不同的处理。

// *****************************************************************************************************

// * 自定义属性（我们可以给函数添加我们自己的属性）
function func() {
	console.log('func')
	func.counter++ // 计算调用次数
}

func.counter = 0 // 初始值（这个属性不是声明在函数内部的，而是声明在函数自身的）
func()
func()
console.log(func.counter) // 2

// * 函数自定义属性用来代替闭包的例子：
function makeCounter() {
	// let count = 0 不需要这个了

	function counter() {
		return counter.count++
	}

	// 现在 count 被直接存储在函数里，而不是它外部的词法环境。
	counter.count = 0

	return counter
}

let counter = makeCounter()

console.log(counter()) // 0
console.log(counter()) // 1

// 这就是闭包做不到一点，因为闭包的变量是在函数内部的，外部环境获取不到。而函数自定义属性可以。
counter.count = 10

console.log(counter()) // 10
console.log(counter()) // 11

// *****************************************************************************************************

// * 命名函数表达式（指带有名字的函数表达式的术语）

// * 普通函数表达式：
let sayHi1 = function (who) {
	console.log(`hi ${who}`)
}

// * 命名函数表达式：
// * 1. 它仍然是一个函数表达式。
// * 2. 在 function 后面加一个名字 "fnVal" 没有使它成为一个函数声明，因为它仍然是作为赋值表达式中的一部分被创建的。
// * 3. 添加 fnVal 名字没有打破任何事，依然通过 sayHiVal 来调用。
// * 命名函数表达式，有两个特点：（1）它允许函数在内部引用自己。（2）它在函数外是不可见的。
// 使用场景例子：
let sayHiVal = function fnVal(who) {
	if (who) {
		console.log(`hi ${who}`)
	} else {
		fnVal('jerry')
	}
}

sayHiVal('Lily') // hi Lily
sayHiVal() // hi jerry
// fnVal() // 报错（外面无法访问）

// *****************************************************************************************************

// 总结：
// 函数的类型是对象。
// 函数自身的属性有：1. name（函数名）2. length（参数个数，...rest参数不计入） 3. 函数还可以自定义属性。
